﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.EventHandling;
using ScrumTable.DL.Data.Generic.Properties;

#endregion


namespace ScrumTable.DL.Data.Generic.Cache
{
    /// <summary>
    /// Specifies the compare method for element list item comparsion.
    /// </summary>
    [Flags]
    public enum CacheElementCompareMethod
    {
        /// <summary>
        /// Specifies the elements should be compared by their unique id.
        /// This is the fastest and most appropriate version. This method
        /// will be used as first choice even if you specify an other
        /// comparsion method.
        /// </summary>
        UniqueId = 0x0,

        /// <summary>
        /// Specifies the elements should be compared by their single id.
        /// This is a slow mechanism with complexity of O(n).
        /// </summary>
        Id = 0x1,

        /// <summary>
        /// Specifies the elements should be compared by their equals method.
        /// This is a slow mechanism with complexity of O(n).
        /// </summary>
        Equals = 0x2
    }


    internal enum CacheElementListChangeAction
    {
        /// <summary>
        /// Determins that no change action has been executed.
        /// </summary>
        None = 0,

        /// <summary>
        /// Determins that an ADD change action has been executed.
        /// </summary>
        Add,

        /// <summary>
        /// Determins that a REMOVE change action has been executed.
        /// </summary>
        Remove
    }


    internal class CacheElementListChangedEventArgs<T> : EventArgs
        where T : class, ICacheElement
    {
        /// <summary>
        /// Gets the change action which has been executed on an element.
        /// </summary>
        public CacheElementListChangeAction ChangeAction { get; internal set; }

        /// <summary>
        /// Specifies the changed element with the bucket enveloppe.
        /// </summary>
        public CacheElementListEntryBucket<T> Bucket { get; internal set; }

        /// <summary>
        /// Specifies the parent id chain of a changed element.
        /// </summary>
        public ValueChain<string> ParentChain { get; internal set; }
    }


    /// <summary>
    /// This class wraps provides a CacheElementList list.
    /// </summary>
    public class CacheElementList<T> : ICacheElementList<T>
        where T : class, ICacheElement
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------
        
        private readonly FastSmartWeakEvent<EventHandler<CacheElementListChangedEventArgs<T>>> _integrityInvalidateEvents = new FastSmartWeakEvent<EventHandler<CacheElementListChangedEventArgs<T>>>();
        private readonly FastSmartWeakEvent<EventHandler<CacheElementListChangedEventArgs<T>>> _changeEvents = new FastSmartWeakEvent<EventHandler<CacheElementListChangedEventArgs<T>>>();

        private readonly IList<CacheElementListEntryBucket<T>> _sortedList = new List<CacheElementListEntryBucket<T>>();
        private readonly IDictionary<string, CacheElementListEntryBucket<T>> _indexedList = new Dictionary<string, CacheElementListEntryBucket<T>>();
        private readonly ICacheElement _parent; // Caution: may contain a null pointer (depends on cnstr-args)
        private readonly CacheElementCompareMethod _compareMethod = CacheElementCompareMethod.UniqueId;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns true, if a bucket element or an element itself of this instance is dirty.
        /// </summary>
        public bool IsDirty
        {
            get
            {
                foreach (CacheElementListEntryBucket<T> entryBucket in _sortedList)
                {
                    if (entryBucket.IsDirty)
                        return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Gets/sets the CacheProject List with the given id.
        /// </summary>
        /// <param name="uniqueId">Id of the element to retrieve or set.</param>
        /// <returns>Returns the retrieved instance.</returns>
        public T this[string uniqueId]
        {
            get { return _indexedList[uniqueId].Element; }
        }

        /// <summary>
        /// Gets the number of elements stored in the list.
        /// </summary>
        public int Count
        {
            get { return _sortedList.Count; }
        }

        /// <summary>
        /// Gets the buckets which contain changes.
        /// </summary>
        public IEnumerable<CacheElementListEntryBucket<T>> ChangedBuckets
        {
            get { return (from bucket in _sortedList where bucket.IsDirty select bucket); }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new CacheElementList instance. This constructor is used in
        /// the current library only.
        /// </summary>
        internal CacheElementList(IEnumerable<T> entries, ICacheElement parent, CacheElementCompareMethod compareMethod = CacheElementCompareMethod.UniqueId)
        {
            PreCondition.AssertNotNull(parent, "parent");

            _compareMethod = compareMethod;
            _parent = parent;
            AddRange(entries);
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Tries to retrieve the element with the given id.
        /// </summary>
        /// <param name="uniqueId">Id of the element to retrieve.</param>
        /// <returns>Returns an instance or a null reference if the value could not be retrieved.</returns>
        object ICacheElementList.TryGetById(string uniqueId)
        {
            return TryGetById(uniqueId);
        }

        /// <summary>
        /// Tries to retrieve the element with the given id.
        /// </summary>
        /// <param name="uniqueId">Id of the element to retrieve.</param>
        /// <returns>Returns an instance or a null reference if the value could not be retrieved.</returns>
        TChild ICacheElementList.TryGetById<TChild>(string uniqueId)
        {
            return (TryGetById(uniqueId) as TChild);
        }


        /// <summary>
        /// Clones the current and all identities below the current cache element
        /// and fills them into the target store.
        /// </summary>
        /// <param name="target">Specifies the store which should be filled with the identities.</param>
        public virtual void CloneIdentities(CacheIdentityStore target)
        {
            foreach (KeyValuePair<string, CacheElementListEntryBucket<T>> elementBucket in _indexedList)
            {
                elementBucket.Value.Element.CloneIdentities(target);
            }
        }

        /// <summary>
        /// Imports the values of another CacheElementList.
        /// </summary>
        /// <returns>Returns the cloned object.</returns>
        /// <remarks>The import avoids value tracking.</remarks>
        public void ImportValues(CacheElementList<T> toImport)
        {
            if (toImport == null)
                return;

            foreach (CacheElementListEntryBucket<T> bucket in toImport._sortedList)
            {
                AddItem(bucket.Clone());
            }
        }

        /// <summary>
        /// Gets a member by its display name.
        /// </summary>
        public T TryGetBy(Predicate<T> toCompare)
        {
            return (from element in this
                    where toCompare(element)
                    select element).FirstOrDefault();
        }

        /// <summary>
        /// Gets a member by its display name.
        /// </summary>
        public T TryGetByWithIndex(Predicate<T> toCompare, out int index)
        {
            index = -1;
            int idx = -1;

            foreach (T element in this)
            {
                ++idx;
                if (toCompare(element))
                {
                    index = idx;
                    return element;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets a member by its display name.
        /// </summary>
        public T TryGetByName(string name)
        {
            return (from element in this
                    where element.Name == name
                    select element).FirstOrDefault();
        }

        /// <summary>
        /// Gets a member by its id. This procedure call may be
        /// slow in conjunction with large lists.
        /// </summary>
        public T TryGetById(int id)
        {
            return (from element in this
                    where element.Id == id
                    select element).FirstOrDefault();
        }

        /// <summary>
        /// Tries to retrieve the element with the given id.
        /// </summary>
        /// <param name="uniqueId">Id of the element to retrieve.</param>
        /// <returns>Returns an instance or a null reference if the value could not be retrieved.</returns>
        public T TryGetById(string uniqueId)
        {
            if (_indexedList.ContainsKey(uniqueId))
                return _indexedList[uniqueId].Element;
            return null;
        }

        /// <summary>
        /// Tries to retrieve the element with the given id.
        /// </summary>
        /// <param name="uniqueId">Id of the element to retrieve.</param>
        /// <returns>Returns an instance or a null reference if the value could not be retrieved.</returns>
        public int IndexOf(string uniqueId)
        {
            return _sortedList.IndexOf(p => p.Element.UniqueId == uniqueId);
        }

        /// <summary>
        /// Tries to retrieve the element with the given id.
        /// </summary>
        /// <param name="uniqueId">Id of the element to retrieve.</param>
        /// <param name="index">Specifies the index of the element to retrieve.</param>
        /// <returns>Returns an instance or a null reference if the value could not be retrieved.</returns>
        public T TryGetByIdWithIndex(string uniqueId, out int index)
        {
            index = -1;

            if (Contains(uniqueId))
            {
                index = IndexOf(uniqueId);
                return this[uniqueId];
            }
            return default(T);
        }


        /// <summary>
        /// Returns true, if the current list contains an element corresponding to the given element.
        /// </summary>
        /// <param name="toCompare">Specifies the element to check.</param>
        public bool Contains(T toCompare)
        {
            CacheElementListEntryBucket<T> bucket;
            return (TryRetrieveReferencedBucket(out bucket, toCompare));
        }

        /// <summary>
        /// Returns true, if the current list contains a value with the given uniqueId.
        /// </summary>
        /// <param name="uniqueId">Id of the element to check.</param>
        public bool Contains(string uniqueId)
        {
            return _indexedList.ContainsKey(uniqueId);
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            foreach (CacheElementListEntryBucket<T> elementPair in _sortedList)
            {
                if (elementPair.State != CacheElementListEntryState.Removed)
                    yield return elementPair.Element;
            }
        }

        /// <summary>
        /// Removes the dirty flag. This should be done after a clone has
        /// been created of the current element so the changes should be
        /// commited to the server.
        /// </summary>
        public void RemoveDirtyFlag()
        {
            IList<CacheElementListEntryBucket<T>> toRemove = new List<CacheElementListEntryBucket<T>>();

            foreach (CacheElementListEntryBucket<T> item in _sortedList)
            {
                if (item.State == CacheElementListEntryState.Removed)
                {
                    toRemove.Add(item);
                }
                else
                {
                    item.Element.RemoveDirtyFlag();
                    item.RemoveDirtyFlag();
                }
            }
            RemoveItems(toRemove);
        }

        /// <summary>
        /// Merges the values of the given object into the current cache
        /// element. The cache element must be of the same type as the
        /// current instance is.
        /// </summary>
        /// <param name="toMerge">Specifies the object to import.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback from the underlying system.</param>
        public virtual void MergeChanges(CacheElementList<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            PreCondition.AssertNotNull(toMerge, "toMerge");
            PreCondition.AssertNotNull(saveProgressFeedback, "saveProgressFeedback");
            PreCondition.AssertTrue(toMerge.GetType() == GetType(), Resources.ExcCacheElemBaseInvalidImportType);

            toMerge._sortedList.ForEach(bucket => MergeBucket(bucket, saveProgressFeedback));
            _sortedList.ForEachIndexReverse(index => MergeRemoveNonExistingBucket(toMerge, _sortedList[index], saveProgressFeedback));
        }


        /// <summary>
        /// Adds the given element to the list.
        /// </summary>
        /// <param name="elementToAdd">Specifies the element to add.</param>
        public void Add(T elementToAdd)
        {
            Add(elementToAdd, true);
        }

        /// <summary>
        /// Removes the given element from the list.
        /// </summary>
        /// <param name="elementToRemove">Specifies the element to remove.</param>
        public void Remove(T elementToRemove)
        {
            Remove(elementToRemove, true);
        }

        /// <summary>
        /// Adds the given element to the list and fires an event if required.
        /// </summary>
        /// <param name="elementToAdd">Specifies the element to add.</param>
        /// <param name="causesIntegrityInvalidateEvent">True to fire the ADD event.</param>
        internal void Add(T elementToAdd, bool causesIntegrityInvalidateEvent)
        {
            PreCondition.AssertNotNull(elementToAdd, "elementToAdd");

            CacheElementListEntryBucket<T> bucket;

            if (TryRetrieveReferencedBucket(out bucket, elementToAdd))
            {
                if (bucket.OriginalState == CacheElementListEntryState.Unmodified
                    && bucket.State != CacheElementListEntryState.Unmodified)
                {
                    bucket.State = CacheElementListEntryState.Unmodified;
                }
                else
                {
                    throw new InvalidOperationException(Resources.ExcCacheElemListDuplicateEntry);
                }
            }
            else
            {
                if (elementToAdd.OwnerContext != null && _parent.OwnerContext == elementToAdd.OwnerContext)
                    throw new InvalidOperationException(Resources.ExcCacheElemListDuplicateEntry);

                bucket = CreateBucket(elementToAdd, CacheElementListEntryState.New);
                AddItem(bucket);
            }

            OnIntegrityInvalidate(causesIntegrityInvalidateEvent, CacheElementListChangeAction.Add, bucket);
        }

        /// <summary>
        /// Removes the given element from the list and fires an event if required.
        /// </summary>
        /// <param name="elementToRemove">Specifies the element to remove.</param>
        /// <param name="causesIntegrityInvalidateEvent">True to fire the REMOVE event.</param>
        internal void Remove(T elementToRemove, bool causesIntegrityInvalidateEvent)
        {
            PreCondition.AssertNotNull(elementToRemove, "elementToRemove");

            CacheElementListEntryBucket<T> bucket;

            if (TryRetrieveReferencedBucket(out bucket, elementToRemove))
            {
                if (bucket.OriginalState == CacheElementListEntryState.Unmodified)
                {
                    bucket.State = CacheElementListEntryState.Removed;
                }
                else
                {
                    RemoveItem(bucket.Element);
                }

                OnIntegrityInvalidate(causesIntegrityInvalidateEvent, CacheElementListChangeAction.Remove, bucket);
            }
        }

        /// <summary>
        /// Retrieves the first available (not removed) element.
        /// </summary>
        /// <returns></returns>
        internal T FirstAvailable()
        {
            return (from listEntryBucket in _sortedList
                    where listEntryBucket.State != CacheElementListEntryState.Removed
                    select listEntryBucket.Element).FirstOrDefault();
        }

        /// <summary>
        /// Tries to retrieve a referenced bucket from the clone to an item inside the current list.
        /// </summary>
        /// <param name="referencedBucketInCache">Specifies the referenced bucket as out argument.</param>
        /// <param name="refBucketToRetrieve">Specifies the element which represents a clone of the current instance.</param>
        /// <returns>Returns the retrieved bucket.</returns>
        protected bool TryRetrieveReferencedBucket(out CacheElementListEntryBucket<T> referencedBucketInCache, CacheElementListEntryBucket<T> refBucketToRetrieve)
        {
            referencedBucketInCache = null;

            if (refBucketToRetrieve.Element == null)
                return false;

            return TryRetrieveReferencedBucket(out referencedBucketInCache, refBucketToRetrieve.Element);
        }

        /// <summary>
        /// Tries to retrieve a referenced bucket from the clone to an item inside the current list.
        /// </summary>
        /// <param name="referencedBucketInCache">Specifies the referenced bucket as out argument.</param>
        /// <param name="elementToRetrieve">Specifies the element which represents a clone of the current instance.</param>
        /// <returns>Returns the retrieved bucket.</returns>
        protected virtual bool TryRetrieveReferencedBucket(out CacheElementListEntryBucket<T> referencedBucketInCache, T elementToRetrieve)
        {
            referencedBucketInCache = null;

            if (elementToRetrieve == null)
                return false;

            // first: try to compare by UniqueId
            referencedBucketInCache = TryGetBucketByUniqueId(elementToRetrieve.UniqueId);
            if (referencedBucketInCache != null) return true;

            // second: try to compare by Id
            if ((_compareMethod & CacheElementCompareMethod.Id) != 0) referencedBucketInCache = TryGetBucketById(elementToRetrieve.Id);
            if (referencedBucketInCache != null) return true;

            // third: try to compare by Equals
            if ((_compareMethod & CacheElementCompareMethod.Equals) != 0) referencedBucketInCache = TryGetBucketByEquals(elementToRetrieve);
            if (referencedBucketInCache != null) return true;

            return false;
        }

        /// <summary>
        /// Merges the given changes with the current cache. This procedure is used
        /// top merge top level entries.
        /// </summary>
        /// <param name="changes">Specifies the buckets to merge.</param>
        internal void MergeChanges(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            PreCondition.AssertNotNull(changes, "changes");

            OnMergeChangesError(
                from change in changes
                where change.SaveFeedback.State == CacheSavePairState.Error
                select change);

            OnMergeChangesInvalidateData(
                from change in changes
                where change.SaveFeedback.State == CacheSavePairState.Updated
                select change);

            OnMergeChangesSaved(
                from change in changes
                where change.SaveFeedback.State == CacheSavePairState.Saved
                select change);

            OnMergeChangesDeleted(
                from change in changes
                where change.SaveFeedback.State == CacheSavePairState.Deleted
                select change);
        }

        /// <summary>
        /// Returns a new instance of a list which contains the bucket clones of all changed items.
        /// </summary>
        /// <returns>
        /// Returns the list with the cloned bucket instances.
        /// </returns>
        internal CacheElementChangeSet<string, T> ExtractChangedBuckets()
        {
            CacheElementChangeSet<string, T> changeSet = new CacheElementChangeSet<string, T>();
            IList<CacheElementListEntryBucket<T>> toRemove = new List<CacheElementListEntryBucket<T>>();

            foreach (CacheElementListEntryBucket<T> item in _sortedList)
            {
                if (item.IsDirty)
                {
                    changeSet.Add(item.Element.UniqueId, item.Clone());

                    if (item.State == CacheElementListEntryState.Removed)
                    {
                        toRemove.Add(item);
                    }
                    else
                    {
                        item.Element.RemoveDirtyFlag();
                        item.RemoveDirtyFlag();
                    }
                }
            }

            RemoveItems(toRemove);
            return changeSet;
        }

        private void MergeRemoveNonExistingBucket(CacheElementList<T> toMerge, CacheElementListEntryBucket<T> bucketInCache, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            CacheElementListEntryBucket<T> hasFound;

            if (bucketInCache.State == CacheElementListEntryState.Unmodified && !toMerge.TryRetrieveReferencedBucket(out hasFound, bucketInCache))
            {
                RemoveItem(bucketInCache.Element);

                // errors add only removed elements. these elements should not be visible to the
                // end user and the consistency events should be ignored
                if (saveProgressFeedback.State != CacheSavePairState.Error) 
                {
                    OnIntegrityInvalidate(true, CacheElementListChangeAction.Remove, bucketInCache);
                }
            }
        }

        private CacheElementListEntryBucket<T> CreateBucket(T elementToAdd, CacheElementListEntryState state, CacheElementListEntryState originalState)
        {
            CacheElementListEntryBucket<T> bucket = CreateBucket();
            bucket.Element = elementToAdd;
            bucket.State = state;
            bucket.OriginalState = originalState;
            return bucket;
        }

        private CacheElementListEntryBucket<T> CreateBucket(T elementToAdd, CacheElementListEntryState initialState)
        {
            CacheElementListEntryBucket<T> bucket = CreateBucket();
            bucket.Element = elementToAdd;
            bucket.State = initialState;
            bucket.OriginalState = initialState;
            return bucket;
        }

        private CacheElementListEntryBucket<T> CreateBucket()
        {
            return new CacheElementListEntryBucket<T>();
        }


        #region Element Add / Remove / Retrieve procedures

        /// <summary>
        /// Forces to add a new element which has been added on the back end system.
        /// </summary>
        /// <param name="elementToAdd">Specifies the element to add.</param>
        /// <param name="state">Specifies the state of the element to add.</param>
        /// <param name="originalState">Specifies the original state of the element to add.</param>
        /// <param name="causesIntegrityInvalidateEvent">True to fire the REMOVE event.</param>
        internal void ForceAdd(T elementToAdd, bool causesIntegrityInvalidateEvent, CacheElementListEntryState state = CacheElementListEntryState.Unmodified, CacheElementListEntryState originalState = CacheElementListEntryState.Unmodified)
        {
            PreCondition.AssertNotNull(elementToAdd, "elementToAdd");

            // check whether the given is already referenced and remove it (force overwrite)
            RemoveItem(elementToAdd);

            CacheElementListEntryBucket<T> newBucket = CreateBucket(elementToAdd, state, originalState);
            AddItem(newBucket);
            OnIntegrityInvalidate(causesIntegrityInvalidateEvent, CacheElementListChangeAction.Add, newBucket);
        }

        /// <summary>
        /// Forces to remove an element which has been removed from the back end system.
        /// </summary>
        /// <param name="elementToRemove">Specifies the element to remove.</param>
        /// <param name="causesIntegrityInvalidateEvent">True to fire the REMOVE event.</param>
        internal void ForceRemove(T elementToRemove, bool causesIntegrityInvalidateEvent)
        {
            PreCondition.AssertNotNull(elementToRemove, "elementToRemove");

            CacheElementListEntryBucket<T> removedItem = RemoveItem(elementToRemove);

            if (removedItem != null)
            {
                OnIntegrityInvalidate(causesIntegrityInvalidateEvent, CacheElementListChangeAction.Remove, removedItem);
            }
        }

        private void AddRange(IEnumerable<T> entries)
        {
            if (entries == null)
                return;

            foreach (T collectionEntry in entries)
            {
                AddItem(collectionEntry);
            }
        }

        private void AddItem(T toAdd)
        {
            AddItem(CreateBucket(toAdd, CacheElementListEntryState.Unmodified));
        }

        private void AddItem(CacheElementListEntryBucket<T> toAdd)
        {
            if (toAdd.Element == null)
                throw new InvalidOperationException(Resources.ExcCacheElemListInvalidBucket);

            toAdd.Element.Parent = _parent;

            // add the entry to the list
            _indexedList[toAdd.Element.UniqueId] = toAdd;
            _sortedList.Add(toAdd);
        }

        private CacheElementListEntryBucket<T> RemoveItem(T elementToRemove)
        {
            CacheElementListEntryBucket<T> bucketInCache;

            // remove existing entry if an entry exists
            if (TryRetrieveReferencedBucket(out bucketInCache, elementToRemove))
            {
                _indexedList.Remove(bucketInCache.Element.UniqueId);
                _sortedList.Remove(bucketInCache);
            }
            return bucketInCache;
        }

        private void RemoveItems(IEnumerable<CacheElementListEntryBucket<T>> toRemove)
        {
            foreach (CacheElementListEntryBucket<T> elementToRemove in toRemove)
            {
                RemoveItem(elementToRemove.Element);
            }
        }

        /// <summary>
        /// Retrieves a bucket by the CacheElements unique id.
        /// </summary>
        /// <param name="uniqueId">Specifies the unique id to retrieve.</param>
        /// <returns>Returns the retrieved element or a unique id.</returns>
        internal CacheElementListEntryBucket<T> TryGetBucketByUniqueId(string uniqueId)
        {
            if (_indexedList.ContainsKey(uniqueId))
            {
                return _indexedList[uniqueId];
            }
            return null;
        }

        private CacheElementListEntryBucket<T> TryGetBucketById(int id)
        {
            foreach (var elementBucket in _sortedList)
            {
                if (elementBucket.Element.Id == id)
                {
                    return elementBucket;
                }
            }
            return null;
        }

        private CacheElementListEntryBucket<T> TryGetBucketByEquals(T toCompare)
        {
            foreach (var elementBucket in _sortedList)
            {
                if (elementBucket.Element.Equals(toCompare))
                {
                    return elementBucket;
                }
            }
            return null;
        }

        #endregion

        #region Import Changes Procedures (on Top Level buckets)

        /// <summary>
        /// Imports the saved cache elements into the corresponding tree.
        /// </summary>
        /// <param name="changes">Specifies the elements to merge.</param>
        /// <remarks>The import avoids value tracking.</remarks>
        private void ImportDataChanges(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            ImportIds(changes);
            ImportChangesOnBuckets(changes);
            AcceptChanges(changes);
        }

        private void AcceptChanges(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            changes.ForEach(
                elementToAccept =>
                {
                    elementToAccept.SaveFeedback.Accepted = true;

                    if (_indexedList.ContainsKey(elementToAccept.Bucket.Element.UniqueId))
                    {
                        _indexedList[elementToAccept.Bucket.Element.UniqueId].Element.ChangesAccepted();
                    }
                });
        }

        private void ImportIds(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            // pre process iteration and index them by their unique ids
            IDictionary<string, int> changeElementIds = changes.Convert(chng => new KeyValuePair<string, int>(chng.Bucket.Element.UniqueId, chng.Bucket.Element.Id));

            // check for new ids and assign them
            foreach (CacheElementListEntryBucket<T> listEntry in _sortedList)
            {
                if (changeElementIds.ContainsKey(listEntry.Element.UniqueId))
                {
                    listEntry.Element.AssignId(changeElementIds[listEntry.Element.UniqueId]);
                }
            }
        }

        private void ImportChangesOnBuckets(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            foreach (CacheElementMergeInfo<T> changeToMerge in changes)
            {
                MergeBucket(changeToMerge.Bucket, changeToMerge.SaveFeedback);
            }
        }

        #endregion

        #region Import Bucket Change Procedures

        private void MergeBucket(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            PreCondition.AssertNotNull(toMerge, "toMerge");
            PreCondition.AssertNotNull(saveProgressFeedback, "saveProgressFeedback");

            switch (saveProgressFeedback.State)
            {
                case CacheSavePairState.Saved:
                    OnMergeBucketChangeSaved(toMerge, saveProgressFeedback);
                    break;
                case CacheSavePairState.Deleted:
                    OnMergeBucketChangeDeleted(toMerge, saveProgressFeedback);
                    break;
                case CacheSavePairState.Updated:
                    OnMergeBucketChangeInvalidateData(toMerge, saveProgressFeedback);
                    break;
                case CacheSavePairState.Error:
                    OnMergeBucketChangeError(toMerge, saveProgressFeedback);
                    break;
                default: throw new ArgumentOutOfRangeException();
            }
        }

        private void ImportFailedBucketChanges(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            CacheElementListEntryBucket<T> referencedBucketInCache;

            if (TryRetrieveReferencedBucket(out referencedBucketInCache, toMerge))
            {
                if (toMerge.CacheRevision == referencedBucketInCache.CacheRevision)
                {
                    // if there were no change made during the save procedure
                    ImportBucketChangesOnExistingBucket(toMerge, saveProgressFeedback, referencedBucketInCache);
                }
            }
            else
            {
                // the element has been deleted but there's an error during the save procedure -> recreated it
                ImportBucketChangesOnNewBucket(toMerge, saveProgressFeedback);
            }
        }

        private void ImportBucketChanges(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            /*
             * Elements may return in modified state. This state is for data export
             * to the back end system. If the element has been saved, this state could
             * be ignored because the elements are already stored or removed from the current list.
             */
            if (toMerge.State != CacheElementListEntryState.Unmodified)
            {
                return;
            }

            CacheElementListEntryBucket<T> referencedBucketInCache;

            if (TryRetrieveReferencedBucket(out referencedBucketInCache, toMerge))
            {
                if (!referencedBucketInCache.CurrentIsDirty)
                {
                    // if we have found the bucket and there were no changes made during the save procedure
                    //  -> lets import the changes to the cache
                    ImportBucketChangesOnExistingBucket(toMerge, saveProgressFeedback, referencedBucketInCache);
                }
            }
            else
            {
                ImportBucketChangesOnNewBucket(toMerge, saveProgressFeedback);
            }
        }

        private void ForceImportBucketChanges(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            CacheElementListEntryBucket<T> referencedBucketInCache;

            if (TryRetrieveReferencedBucket(out referencedBucketInCache, toMerge))
            {
                ImportBucketChangesOnExistingBucket(toMerge, saveProgressFeedback, referencedBucketInCache);
            }
            else
            {
                ImportBucketChangesOnNewBucket(toMerge, saveProgressFeedback);
            }
        }

        private void ImportBucketChangesOnExistingBucket(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback, CacheElementListEntryBucket<T> referencedBucketInCache)
        {
            // forward the changes to the bucket child elements
            referencedBucketInCache.MergeChanges(toMerge, saveProgressFeedback);
            OnIntegrityInvalidate(true, CacheElementListChangeAction.None, toMerge);
        }

        private void ImportBucketChangesOnNewBucket(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            // a new element has been created on server side, let's create it in the cache too
            CacheElementListEntryBucket<T> bucket = CreateBucket();
            bucket.MergeChanges(toMerge, saveProgressFeedback);
            AddItem(bucket);

            // errors add only removed elements. these elements should not be visible to the
            // end user and the consistency events should be ignored
            if (saveProgressFeedback.State != CacheSavePairState.Error)
            {
                OnIntegrityInvalidate(true, CacheElementListChangeAction.Add, bucket);
            }
        }

        #endregion

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        /// <summary>
        /// This event will be fired if a bucket change requires that the cache must be invalidated.
        /// </summary>
        internal event EventHandler<CacheElementListChangedEventArgs<T>> IntegrityInvalidate
        {
            add { _integrityInvalidateEvents.Add(value); }
            remove { _integrityInvalidateEvents.Remove(value); }
        }

        /// <summary>
        /// Fires the IntegrityInvalidate event.
        /// </summary>
        /// <param name="args">Specifies the event args which should be passed to the event listeners.</param>
        private void OnIntegrityInvalidate(CacheElementListChangedEventArgs<T> args)
        {
            _integrityInvalidateEvents.Raise(this, args);
        }

        private void OnIntegrityInvalidate(bool fireEvent, CacheElementListChangeAction changeAction, CacheElementListEntryBucket<T> bucket)
        {
            CacheElementListChangedEventArgs<T> args = new CacheElementListChangedEventArgs<T>
                                                           {
                                                               ChangeAction = changeAction,
                                                               Bucket = bucket
                                                           };
            OnChange(args);

            if (fireEvent)
            {
                OnIntegrityInvalidate(args);
            }
        }


        /// <summary>
        /// This event will be fired if a bucket is changed.
        /// </summary>
        internal event EventHandler<CacheElementListChangedEventArgs<T>> Change
        {
            add { _changeEvents.Add(value); }
            remove { _changeEvents.Remove(value); }
        }

        /// <summary>
        /// Fires the IntegrityInvalidate event.
        /// </summary>
        /// <param name="args">Specifies the event args which should be passed to the event listeners.</param>
        private void OnChange(CacheElementListChangedEventArgs<T> args)
        {
            _changeEvents.Raise(this, args);
        }


        /// <summary>
        /// An error occurred while saving the given cache element. This means, that
        /// the given element must be merged back (if there are no changes) into
        /// the cache tree.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which caused the error.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected virtual void OnMergeBucketChangeError(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            ImportFailedBucketChanges(toMerge, saveProgressFeedback);
        }

        /// <summary>
        /// An element on server side has changed (which behaviour may caused an error). Its
        /// cache data must be invalidated in order to resynchronize with the server.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which data should be invalidated.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected virtual void OnMergeBucketChangeInvalidateData(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            ForceImportBucketChanges(toMerge, saveProgressFeedback);
        }

        /// <summary>
        /// An element has saved on server side. This means that data in the local storage may
        /// be invalidated if the change caused additional changes on server side. Workflow
        /// changes may cause additional value changes.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which data have ben saved.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected virtual void OnMergeBucketChangeSaved(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            ImportBucketChanges(toMerge, saveProgressFeedback);
        }

        /// <summary>
        /// An element on server side has been deleted. The element in the cache must also be 
        /// deleted.
        /// </summary>
        /// <param name="toMerge">Specifies the cache element which has been deleted.</param>
        /// <param name="saveProgressFeedback">Specifies the save feedback provided by the data driver.</param>
        protected virtual void OnMergeBucketChangeDeleted(CacheElementListEntryBucket<T> toMerge, ICacheSaveProgressFeedback saveProgressFeedback)
        {
            ForceImportBucketChanges(toMerge, saveProgressFeedback);
        }


        /// <summary>
        /// An error occurred while saving the given cache elements. This means, that
        /// the given elements must be merged back (if there are no changes) into
        /// the cache tree.
        /// </summary>
        /// <param name="changes">Specifies the cache elements which caused the error.</param>
        /// <remarks>This procedure is used top merge top level entries.</remarks>
        private void OnMergeChangesError(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            ImportChangesOnBuckets(changes);
        }

        /// <summary>
        /// Elements on server side have changed (which behaviour may caused an error). Its
        /// cache data must be invalidated in order to resynchronize with the server.
        /// </summary>
        /// <param name="changes">Specifies the cache elements which data should be invalidated.</param>
        /// <remarks>This procedure is used top merge top level entries.</remarks>
        private void OnMergeChangesInvalidateData(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            ImportDataChanges(changes);
        }

        /// <summary>
        /// Elements have been saved on server side. This means that data in the local storage may
        /// be invalidated if the change caused additional changes on server side. Workflow
        /// changes may cause additional value changes.
        /// </summary>
        /// <param name="changes">Specifies the cache elements which data have ben saved.</param>
        /// <remarks>This procedure is used top merge top level entries.</remarks>
        private void OnMergeChangesSaved(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            ImportDataChanges(changes);
        }

        /// <summary>
        /// Elements on server side have been deleted. The elements in the cache must also be 
        /// deleted.
        /// </summary>
        /// <param name="changes">Specifies the cache elements which has been deleted.</param>
        /// <remarks>This procedure is used top merge top level entries.</remarks>
        private void OnMergeChangesDeleted(IEnumerable<CacheElementMergeInfo<T>> changes)
        {
            // TODO: (geh) add the remove functionality here, if elements on top-level have been deleted
        }

        #endregion
    }
}